/*--------------------------------------------- Noble Steed Engine--------------------------------------*
Authors : Daniel Randle, Alex Bourne
Date Created : Mar 13 2013

File:
	nsobjectreferenceset.cpp

Description:
	This file contains the definition for the NSObjectReferenceSet class and any associated functions that help
	the ol' girl out
*-----------------------------------------------------------------------------------------------------*/

#include <nsobjectreferenceset.h>
#include <nsobjectreference.h>
#include <sstream>
#include <nsobjectset.h>
#include <nsobject.h>

#ifdef DEBUG
#include <debug.h>
#endif


/*-------------------------------------*
|   class NSObjectReferenceSet BEGINS  |
*--------------------------------------*/


NSObjectReferenceSet::NSObjectReferenceSet(NSMapArea * map):mapArea(map)
{
	LogFile("Creating reference set");
}

NSObjectReferenceSet::~NSObjectReferenceSet()
{
	std::stringstream ss;
	ss << "Deleting reference set with " << this->getTotalReferenceCount() << " total references";
	LogFile l(ss);
	clear();
	LogFile p("ReferenceSet Deleted");
}

void NSObjectReferenceSet::addReference(NSObjectReference  * toAdd)
{
	references[toAdd->getBase()].push_back(toAdd);

	// If grid snap enabled for reference then snap it
	if (toAdd->isSnapToGrid())
		toAdd->snapToGrid();
}

void NSObjectReferenceSet::addReference( NSObject * objPtr,const NSVec3Df & pos,const NSVec3Df & orient,const NSVec3Df & _scale)
{
	addReference(new NSObjectReference(objPtr,pos,orient,_scale));
}

void NSObjectReferenceSet::clear()
{
	LogFile("Clearing references");
	std::map<NSObject*,std::vector<NSObjectReference*>>::iterator iter = references.begin();
	while (iter != references.end())
	{
		while (iter->second.begin() != iter->second.end())
		{
			iter->second.back()->remove();
			delete iter->second.back();
			iter->second.pop_back();
		}
		++iter;
	}
}

bool NSObjectReferenceSet::contains(NSObject * object)
{
	Iterator iter = references.begin();
	while (iter != references.end())
	{
		if (iter->first == object)
			return true;
		++iter;
	}
	return false;
}

unsigned int NSObjectReferenceSet::getObjectCount()
{
	return references.size();
}

NSObjectReference* NSObjectReferenceSet::getReference(unsigned int referenceID, NSObject * baseObj)
{
	if (!contains(baseObj))
		return NULL;
	RefIterator iter = refBegin(baseObj);
	while (iter != refEnd(baseObj))
	{
		if ( (*iter)->getID() == referenceID)
			return *iter;
		++iter;
	}
	return NULL;
}

unsigned int NSObjectReferenceSet::getReferenceCount(NSObject * baseObj)
{
	Iterator iter = begin();
	unsigned int count = 0;
	while (iter != end())
	{
		if (iter->first == baseObj)
			count += iter->second.size();
		++iter;
	}
	return count;
}

unsigned int NSObjectReferenceSet::getTotalReferenceCount()
{
	Iterator iter = begin();
	unsigned int count = 0;
	while (iter != end())
	{
		count += iter->second.size();
		++iter;
	}
	return count;
}

bool NSObjectReferenceSet::removeAllReferencesOf(NSObject * base)
{
	bool ret = false;
	while (references[base].begin() != references[base].end())
	{
		references[base].back()->remove();  // remove transform from object transform list
		delete references[base].back();
		references[base].pop_back();
		ret = true;
	}
	return ret;
}

bool NSObjectReferenceSet::removeReference(unsigned int referenceID, NSObject * baseObj)
{
	return removeReference(getReference(referenceID,baseObj));
}

bool NSObjectReferenceSet::removeReference(NSObjectReference* reference)
{
	bool removed = false;
	if (!contains(reference->getBase()))
		return false;

	Iterator iter = begin();
	while (iter != end())
	{
		if (iter->first == reference->getBase())
		{
			RefIterator refIter = iter->second.begin();
			while (refIter != iter->second.end())
			{
				if ( *refIter == reference )
				{
					(*refIter)->remove();
					delete (*refIter);
					refIter = iter->second.erase(refIter);
					removed = true;
					if (refIter == iter->second.end())
						return true;
				}
				if (removed)
					(*refIter)->decrementID();
				++refIter;
			}
			if (removed)
				return true;
		}
		++iter;
	}
	return false;
}

void NSObjectReferenceSet::setRefsProjMat(const NSMatrix4Df & projMat)
{
	Iterator iter = references.begin();
	while (iter != references.end())
	{
		RefIterator iter2 = iter->second.begin();
		while (iter2 != iter->second.end())
		{
			(*iter2)->setProjectionMat(projMat);
			++iter2;
		}
		++iter;
	}
}

void NSObjectReferenceSet::update(NSObject * base)
{
	Iterator iter = references.begin();
	while (iter != references.end())
	{
		if (iter->first == base)
		{
			RefIterator iter2 = iter->second.begin();
			while (iter2 != iter->second.end())
			{
				(*iter2)->update();
				++iter2;
			}
		}
		++iter;
	}
}

void NSObjectReferenceSet::updateAll()
{
	Iterator iter = references.begin();

	while (iter != references.end())
	{
		if (iter->second.size() > 300)
		{
			DWORD   dwThreadIdArray[4];
			HANDLE  hThreadArray[4];
			int refsPerThread = iter->second.size() / 4;
			int leftOver = iter->second.size() % 4;

			for (int i = 0; i < 4; ++i)
			{
				Param * p = new Param();
				p->list = &iter->second;
				p->startIndex = i * refsPerThread;
				p->size = refsPerThread;
				p->th = this;
				if (i == 3)
					p->size = refsPerThread + leftOver;
				
				hThreadArray[i] = CreateThread(NULL,0,MyThreadFunction,(LPVOID)p,0,&dwThreadIdArray[i]);
			}
			WaitForMultipleObjects(8, hThreadArray, TRUE, INFINITE);
			for(int i=0; i<4; i++)
				CloseHandle(hThreadArray[i]);
		}
		else
		{
			RefIterator iter2 = iter->second.begin();
			while (iter2 != iter->second.end())
			{
				(*iter2)->update();
				++iter2;
			}
		}
		++iter;
	}
}

void NSObjectReferenceSet::DoThread(std::vector<NSObjectReference*>* list, int si, int size)
{
	for (int i = si; i < si+size; ++i)
		(*list)[i]->update();
}

DWORD WINAPI NSObjectReferenceSet::MyThreadFunction(LPVOID lpParam)
{
	
	Param * lp = (Param*)lpParam;
	lp->th->DoThread(lp->list, lp->startIndex, lp->size);
	delete lp;
	return 0;
}

NSObjectReferenceSet::Iterator NSObjectReferenceSet::begin()
{
	return references.begin();
}

NSObjectReferenceSet::Iterator NSObjectReferenceSet::end()
{
	return references.end();
}

NSObjectReferenceSet::RefIterator NSObjectReferenceSet::refBegin(NSObject * baseObj)
{
	Iterator iter = begin();
	while (iter != end())
	{
		if (iter->first == baseObj)
			return iter->second.begin();
		++iter;
	}
	LogFile("Unable to return beginning iterator for references of object : " + baseObj->getName());
	return empty.begin();
}

NSObjectReferenceSet::RefIterator NSObjectReferenceSet::refEnd(NSObject * baseObj)
{
	Iterator iter = references.begin();
	while (iter != references.end())
	{
		if (iter->first == baseObj)
			return iter->second.end();
		++iter;
	}
	LogFile("Unable to return end iterator for references of object : " + baseObj->getName());
	return empty.end();
}
